package com.shouke.jmeter.plugin.httpFileUpload.sampler;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.ArrayList;
import java.util.Vector;

import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.jmeter.samplers.AbstractSampler;
import org.apache.jmeter.samplers.Entry;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.property.IntegerProperty;
import org.apache.jmeter.testelement.property.StringProperty;
import org.apache.log4j.Logger;

import com.shouke.jmeter.plugin.httpFileUpload.util.HttpUtil;

/**
 *  JMeter creates an instance of a sampler class for every occurrence of the
 * element in every thread. [some additional copies may be created before the
 * test run starts]
 *
 * Thus each sampler is guaranteed to be called by a single thread - there is no
 * need to synchronize access to instance variables.
 *
 * However, access to class fields must be synchronized.
 *
 */
public class BinaryFileUploadSampler extends AbstractSampler {
    // 大写字母定义的变量，将在get和set函数中当定义JMeter属性，所以变量值必须唯一，且不能放在初始化构造函数中，否则会导致界面元素（比如JTextField）的值相互影响
    // 另外，还是会导致已保存的jmx文件中填写的数据被重新初始化
    private static String PROTOCOL_TYPE = "PROTOCOL_TYPE";                                  // 协议类型
    private static String HOST = "HOST";                                                    // 服务器名称或IP
    private static String SSL_TSL = "SSL_TSL";                                              // HTTPS SSL/TSL版本
    private static String PORT = "PORT";                                                    // 端口号
    private static String MAX_CONN_NUM = "MAX_CONN_NUM";                                    // 连接池支持的最大连接数
    private static String MAX_CONN_NUM_PER_ROUTE = "MAX_CONN_NUM_PER_ROUTE";                // 每个路由最大连接数
    private static String GET_CONN_TIMEOUT = "GET_CONN_TIMEOUT";                            // 从连接池中获取连接超时时间
    private static String CONNECT_SERVER_TIMEOUT = "CONNECT_SERVER_TIMEOUT";                // 连接服务器超时时间
    private static String WAIT_SERVER_RESPONSE_TIMEOUT = "WAIT_SERVER_RESPONSE_TIMEOUT";    // 等待服务器返回数据超时时间
    private static String URL_PATH = "URL_PATH";                                            // 接口URL路径
    private static String FILE_PATH = "FILE_PATH";                                          // 文件路径
    private static String RESPONSE_ENCODING = "RESPONSE_ENCODING";                          // 服务器返回内容编码
    public static String HEADER_ARGS_SIZE = "HEADER_ARGS_SIZE";                             // 请求头参数个数

    private static final long serialVersionUID = 240L;

    public static CloseableHttpClient httpClient = null;                                    // http请求客户端
    private String url;                                                                     // 接口URL路径
    private String responseEncoding;                                                        // 服务器返回内容编码
    private Vector<HeaderArgument> headers;                                                 // 请求头
    private boolean reSample;                                                               // 用于标记是否重新执行压测
    private StringBuilder stringBuilder;                                                    // 用于记录请求基础数据（方法参数类型及参数值外）
    List<Object> sampleResult;                                                              // 存放请求结果

    private static Logger logger = Logger.getLogger(BinaryFileUploadSampler.class);  // 获取日志打印器

    public BinaryFileUploadSampler() {
        logger.debug("初始化Sampler");
        reSample = true;
        url = "";
        responseEncoding = "utf-8";
        stringBuilder = null;
        sampleResult = null;
        headers = null;
    }

    // 获取连接池支持的最大连接数
    public String getMaxConnNum() {
        return this.getPropertyAsString(MAX_CONN_NUM);

    }

    // 设置连接池支持的最大连接数
    public void setMaxConnNum(String totalConnNum) {
        this.setProperty(new StringProperty(MAX_CONN_NUM, totalConnNum.replaceAll("\\s*", "")));
    }


    // 获取每个路由支持的最大连接数
    public String getMaxConnPerRoute() {
        return this.getPropertyAsString(MAX_CONN_NUM_PER_ROUTE);
    }

    // 设置每个路由支持的最大连接数
    public void setMaxConnPerRoute(String maxConnPerRoute) {
        this.setProperty(new StringProperty(MAX_CONN_NUM_PER_ROUTE, maxConnPerRoute.replaceAll("\\s*", "")));
    }

    // 获取从连接池中获取连接超时时间
    public String getGetConnTimeout() {
        return this.getPropertyAsString(GET_CONN_TIMEOUT);

    }

    // 设置从连接池中获取连接超时时间
    public void setGetConnTimeout(String getConnTimeout) {
        this.setProperty(new StringProperty(GET_CONN_TIMEOUT, getConnTimeout.replaceAll("\\s*", "")));
    }

    // 获取连接服务器超时时间
    public String getConnectServerTimeout() {
        return this.getPropertyAsString(CONNECT_SERVER_TIMEOUT);

    }

    // 设置连接服务器超时时间
    public void setConnectServerTimeout(String connectServerTimeout) {
        this.setProperty(new StringProperty(CONNECT_SERVER_TIMEOUT, connectServerTimeout.replaceAll("\\s*", "")));
    }


    // 获取等待服务器返回数据超时时间
    public String getWaitServerResponseTimeout() {
        return this.getPropertyAsString(WAIT_SERVER_RESPONSE_TIMEOUT);

    }

    // 设置等待服务器返回数据超时时间
    public void setWaitServerResponseTimeout(String waitServerResponseTimeout) {
        this.setProperty(new StringProperty(WAIT_SERVER_RESPONSE_TIMEOUT, waitServerResponseTimeout.replaceAll("\\s*", "")));
    }


    // 获取协议类型
    public String getProtocolType() {
        return this.getPropertyAsString(PROTOCOL_TYPE);
    }

    // 设置协议类型
    public void setProtocolType(String protocolType) {
        this.setProperty(new StringProperty(PROTOCOL_TYPE, protocolType));
    }

    // 获取SSL/TSL
    public String getSslTls() {
        return this.getPropertyAsString(SSL_TSL);
    }

    // 设置SSL/TSL
    public void setSslTls(String sslTsl) {
        this.setProperty(new StringProperty(SSL_TSL, sslTsl));
    }
    // 获取服务器名称或IP
    public String getHost() {
        String hostAddr = this.getPropertyAsString(HOST);
        String host = hostAddr;
        int index = hostAddr.indexOf("://");
        if (index > -1) {
            index = index + 3;
            host = hostAddr.substring(index);
        }
        logger.debug("调用 getHost 方法，返回服务器名称或IP,：" + host);
        return host;
    }

    // 设置服务器名称或IP
    public void setHost(String host) {
        host = host.replaceAll("\\s*", "");;
        String hostAddr = host;
        int index = hostAddr.indexOf("://");
        if (index > -1) {
            index = index + 3;
            host = hostAddr.substring(index);
        }
        logger.debug("调用 setHost 方法，设置服务器名称或IP为：" + host);

        this.setProperty(new StringProperty(HOST, host));
    }



    // 获取端口
    public String getPort() {
        return this.getPropertyAsString(PORT);

    }

    // 设置端口
    public void setPort(String port) {
        this.setProperty(new StringProperty(PORT, port.replaceAll("\\s*", "")));
    }




    // 获取URL
    public String getUrlPath() {
        return this.getPropertyAsString(URL_PATH);

    }

    // 设置URL
    public void setUrlPath(String url) {
        this.setProperty(new StringProperty(URL_PATH, url.replaceAll("\\s*", "")));
    }


    // 设置服务器返回内容编码
    public void setResponseEncoding(String responseEncoding) {
        this.setProperty(new StringProperty(RESPONSE_ENCODING, responseEncoding.replaceAll("\\s*", "")));
    }


    // 获取服务器返回内容编码
    public String getResponseEncoding() {
        return this.getPropertyAsString(RESPONSE_ENCODING);

    }


    // 获取文件路径名称
    public String getFilePath() {
        return this.getPropertyAsString(FILE_PATH);
    }

    // 设置文件路径名称
    public void setFilePath(String filePath) {
        this.setProperty(new StringProperty(FILE_PATH, filePath.trim()));
    }


    // 获取请求头参数
    public Vector<HeaderArgument> getHeaderArguments() {
        int methodArgCnt = this.getPropertyAsInt(HEADER_ARGS_SIZE, 0); // 如果不存在METHOD_ARGS_SIZE 属性则返回默认值0，否则以返回int型的METHOD_ARGS_SIZE的值
        Vector<HeaderArgument> vector = new Vector<>(); //用于存储方法参数,形如 [[argType1, value1],[argType2, value2], ...]
        for (int i=1; i<= methodArgCnt; i++) {
            String argumentType = this.getPropertyAsString("HEADER_NAME" + i);
            String argumentValue = this.getPropertyAsString("HEADER_AVLUE" + i);
            HeaderArgument headerArgument = new HeaderArgument(argumentType, argumentValue);
            vector.add(headerArgument);
        }
        return vector;
    }

    // 设置请求头参数
    public void setHeaderArguments(Vector<HeaderArgument> headerArguments) {
        int methodArgCnt = (headerArguments == null ? 0: headerArguments.size()); // 获取参数个数
        this.setProperty(new IntegerProperty(HEADER_ARGS_SIZE, methodArgCnt));

        if (methodArgCnt > 0) {
            for (int i=1; i<=methodArgCnt; i++) {
                this.setProperty(new StringProperty("HEADER_NAME" + i, headerArguments.get(i-1).getHeaderName()));
                this.setProperty(new StringProperty("HEADER_AVLUE" + i, headerArguments.get(i-1).getHeaderValue()));
            }
        }

    }


    // 调用 HTTP POST请求
    private List<Object> invokeBianryFileUploadAPI() {
        logger.debug("执行 invokeBianryFileUploadAPI 方法");
        if (sampleResult != null) {
            sampleResult.clear();
        }


        StringBuilder temp_StringBuilder = new StringBuilder();   // 用于记录请求头和对应值
        try {
            if (reSample) {  // 每次重新执行压测，针对每个线程，都只运行一次，以获取配置相关数据（方法参数类型，参数值除外）
                stringBuilder = new StringBuilder();   // 用于记录请求头和对应值
                reSample = false;
                stringBuilder = new StringBuilder();
                sampleResult = new ArrayList();

                String maxConnNum = getMaxConnNum();
                String maxConnPerRoute = getMaxConnPerRoute();
                String getConnTimeout =  getGetConnTimeout();
                String connectServerTimeout = getConnectServerTimeout();
                String waitServerResponseTimeout = getWaitServerResponseTimeout();

                String protocolType = getProtocolType();
                String sslTsl = getSslTls();
                String host = getHost();
                String port = getPort();


                stringBuilder.append("所有线程共享参数配置\n");
                Integer maxConnNumInt = 20000;
                try {
                    if (maxConnNum.length() != 0) {
                        maxConnNumInt = Integer.valueOf(maxConnNum);
                        stringBuilder.append("  连接池最大连接数：").append(maxConnNum).append("\n");
                    } else {
                        stringBuilder.append("  连接池最大连接数：").append("20000").append("\n");
                    }
                } catch (NumberFormatException e) {
                    sampleResult.add(false);
                    sampleResult.add("请求失败，\"连接池最大连接数\"只能为整数");
                    sampleResult.add(stringBuilder.toString());
                    return sampleResult;
                }


                Integer maxConnPerRouteInt = 20000;
                try {
                    if (maxConnPerRoute.length() != 0) {
                        maxConnPerRouteInt = Integer.valueOf(maxConnPerRoute);
                        stringBuilder.append("  每个路由最大连接数：").append(maxConnPerRoute).append("\n");
                    } else {
                        stringBuilder.append("  每个路由最大连接数：").append("20000").append("\n");
                    }
                } catch (NumberFormatException e) {
                    sampleResult.add(false);
                    sampleResult.add("请求失败，\"每个路由最大连接数\"只能为整数");
                    sampleResult.add(stringBuilder.toString());
                    return sampleResult;
                }

                Integer getConnTimeoutInt = 120000;
                try {
                    if (getConnTimeout.length() != 0) {
                        getConnTimeoutInt = Integer.valueOf(getConnTimeout);
                        stringBuilder.append("  从连接池中获取连接超时时间：").append(getConnTimeout).append(" 毫秒\n");
                    } else {
                        stringBuilder.append("  从连接池中获取连接超时时间：").append("20000").append(" 毫秒\n");
                    }
                } catch (NumberFormatException e) {
                    sampleResult.add(false);
                    sampleResult.add("请求失败，\"从连接池中获取连接超时时间\"只能为整数");
                    sampleResult.add(stringBuilder.toString());
                    return sampleResult;
                }

                Integer connectServerTimeoutInt = 60000;
                try {
                    if (connectServerTimeout.length() != 0) {
                        connectServerTimeoutInt = Integer.valueOf(connectServerTimeout);
                        stringBuilder.append("  连接服务器超时时间：").append(connectServerTimeout).append(" 毫秒\n");
                    } else {
                        stringBuilder.append("  连接服务器超时时间：").append("60000").append(" 毫秒\n");
                    }
                } catch (NumberFormatException e) {
                    sampleResult.add(false);
                    sampleResult.add("请求失败，\" 连接服务器超时时间\"只能为整数");
                    sampleResult.add(stringBuilder.toString());
                    return sampleResult;
                }

                Integer waitServerResponseTimeoutInt = 100000;

                try {
                    if (waitServerResponseTimeout.length() != 0) {
                        waitServerResponseTimeoutInt = Integer.valueOf(waitServerResponseTimeout);
                        stringBuilder.append("  等待服务器返回数据超时时间：").append(waitServerResponseTimeout).append(" 毫秒\n\n");
                    } else {
                        stringBuilder.append("  等待服务器返回数据超时时间：").append("100000").append(" 毫秒\n\n");
                    }
                } catch (NumberFormatException e) {
                    sampleResult.add(false);
                    sampleResult.add("请求失败，\" 等待服务器返回数据超时时间\"只能为整数");
                    sampleResult.add(stringBuilder.toString());
                    return sampleResult;
                }
                stringBuilder.append("协议类型：").append(protocolType).append("\n");

                if (protocolType == "https") {
                    stringBuilder.append("SSL/TSL：").append(sslTsl).append("\n");
                }

                stringBuilder.append("服务器名称或IP：").append(host).append("\n");
                if (host.length() == 0) {
                    sampleResult.add(false);
                    sampleResult.add("请求失败，服务器名称或者IP不能为空");
                    sampleResult.add(stringBuilder.toString()); // 请求头名称和值
                    return sampleResult;
                }

                url = url + protocolType + "://" + host;

                stringBuilder.append("端口号：").append(port).append("\n");
                try {
                    if (port.length() != 0) {
                        Integer.valueOf(port);
                        url = url + ":" + port;
                    }
                } catch (NumberFormatException e) {
                    sampleResult.add(false);
                    sampleResult.add("请求失败，\"端口号\"只能为整数");
                    sampleResult.add(stringBuilder.toString());
                    return sampleResult;
                }


                String url_path = getUrlPath();
                stringBuilder.append("URL路径：").append(url_path).append("\n");
                if (url_path.length() == 0) {
                    sampleResult.add(false);
                    sampleResult.add("请求失败，\"URL路径\"不能为空");
                    sampleResult.add(stringBuilder.toString());
                    return sampleResult;
                } else if (url_path.indexOf("/") == 0) {
                    url_path = url_path.substring(1);
                }
                url = url + "/" + url_path;

                responseEncoding = getResponseEncoding();
                stringBuilder.append("服务器返回内容编码：").append(responseEncoding).append("\n");

                // 获取请求头
                headers = getHeaderArguments();
                stringBuilder.append("请求头：\n");
                for(HeaderArgument header : headers) { // 循环遍历参数对象，获取请求头名称和值
                    stringBuilder.append(header.getHeaderName()).append(":").append(header.getHeaderValue()).append("\n");
                }

                if (httpClient == null) {
                    httpClient = HttpUtil.getHttpClient(sslTsl, maxConnNumInt,  maxConnPerRouteInt, getConnTimeoutInt, connectServerTimeoutInt, waitServerResponseTimeoutInt);
                }
            }

            String filePath = getFilePath();
            filePath = filePath.replace("\\", "/");
            if (filePath.indexOf("./") != -1) { // 使用相对路径
                filePath =  System.getProperty("user.dir") + filePath.substring(1);
                filePath = filePath.replace("\\", "/");
            }
            temp_StringBuilder.append("文件路径：").append(filePath).append("\n");

            File file = new File(filePath);
            if(!file.exists()){
                sampleResult.add(false);
                sampleResult.add("请求失败，" + String.format("文件“%s”不存在", filePath));
                sampleResult.add(stringBuilder.toString()+temp_StringBuilder.toString());
                return sampleResult;
            }

            // 生成Body
            InputStream body = new FileInputStream(filePath);

            try {
                String result = HttpUtil.uploadBinaryFile(httpClient, url, headers, body, responseEncoding);
                sampleResult.add(true);
                sampleResult.add(result);
                sampleResult.add(stringBuilder.toString() + temp_StringBuilder.toString());
                return sampleResult;
            } catch (Exception e) {
                logger.error("请求上传二进制文件出错：", e);
                sampleResult.add(false);
                sampleResult.add("请求上传二进制文件出错：" + e.toString());
                sampleResult.add(stringBuilder.toString() + temp_StringBuilder.toString());
                return sampleResult;
            }
        } catch (Exception e ) {
            logger.error("UnknownException：", e);
            sampleResult.add(false);
            sampleResult.add(e.toString());
            sampleResult.add(stringBuilder.toString() + temp_StringBuilder.toString());
            return sampleResult;
        }
    }


    public SampleResult sample(Entry e) {
        logger.debug("调用 sample");
        SampleResult res = new SampleResult();
        boolean isOK = false;//测试结果标记位
        res.setSampleLabel(getTitle()); // 设置采样标签
        res.sampleStart(); // 开始统计响应时间

        /*
         * 执行采样
         */
        try {

            /*
             * Set up the sample sampleResult details
             */

            List<Object> responseData = invokeBianryFileUploadAPI();
            isOK = (Boolean)responseData.get(0);
            Object data = responseData.get(1);
            String samplerData = (String) responseData.get(2);  // 存放请求数据
            res.setSamplerData(samplerData); // 设置请求数据
            res.setResponseData(data.toString(), "utf-8");
            res.setDataType(SampleResult.TEXT);
            if (isOK) {
                res.setResponseCodeOK();
                res.setResponseMessage("OK");
            }
        } catch (Exception ex) {
            String responseMessage = ex.toString();
            logger.debug(responseMessage);
            res.setResponseMessage(responseMessage);
            res.setResponseCode("500");// $NON-NLS-1$
        } finally {
            res.sampleEnd(); // End timimg
            res.setSuccessful(isOK);
        }

        return res;
    }

    /**
     * @return a string for the sampleResult Title
     */
    private String getTitle() {
        return this.getName();
    }

//    /*
//    * 释放资源
//    * */
//    @Override
//    protected void finalize() throws java.lang.Throwable{
//        try {
//           // todo
//        } finally {
//            super.finalize();
//        }
//    }

}